/*******************************************************************/
/*    Copyright (c) 1999-2020 by Spatial Corp.                     */
/*    All rights reserved.                                         */
/*    Protected by U.S. Patents 5,257,205; 5,351,196; 6,369,815;   */
/*                              5,982,378; 6,462,738; 6,941,251    */
/*    Protected by European Patents 0503642; 69220263.3            */
/*    Protected by Hong Kong Patent 1008101A                       */
/*******************************************************************/

//----------------------------- IopAcisImportExport.cpp -------------------------------

#include <iostream>
#include <fstream>
#include <vector>
#include <time.h>
#include <list>
#include <utility>

//Acis
#include "lists.hxx"
#include "kernapi.hxx" 

//Iop
#include "SPAIop.h"
#include "SPAIopAcis.h"
#include "SPAIopMisc.h"
#include "SPAIopVisu.h"
#include "SPAIopPolicyOptions.h"
#include "SPAIopArgParser.h"
#include "SPAIopSampleUtilities.h"
#include "SPAIopAcisSampleUtilities.h"
#include "SPAIopSampleResultUtils.h"
#include "SPAIopPerformanceStatistics.h"
#include "SPAIopPMIData.h"
#include "SPAIopMappingData.h"
#include "SPAIopAcisPartImportResult.h"


//MP
#include "SPAIopMultiProcessBatchDefinition.h"
#include "SPAIopMultiProcessSpoolerEx.h"

#include<unordered_map>
#include<unordered_set>

using namespace SPAIopSampleLogging;
using namespace SPAIopAcisPrintUtils;
using namespace SPAIopAcisPSVisualizationUtils;

//-----------------------------------------------------------------------------------------

int ImportAndExportModel(const wchar_t*& ipSourceFilePath, const wchar_t*& ipTargetFilePathconst, const wchar_t*& ipSourceDocType, const wchar_t*& ipTargetDocType, const SPAIopPolicy& iImportPolicy, const SPAIopPolicy& iExportPolicy);
int  DefineOutputProductStructure(SPAIopInputProductStructure& iInputPS,SPAIopPSImportResult &iInputPSResult, const SPAIopPolicy& iPolicy, SPAIopOutputProductStructure& oOutputPS, int& opCopiedInstances);
int DefinePSReferenceMP(SPAIopInputPSReference& iInputReference, SPAIopInputPSMissingFileIter& iMissingFiles, SPAIopOutputProductStructure& iOutputPS, const SPAIopPolicy& iPolicy, SPAIopOutputPSReference& oOutputReference, int& opNumDefined, std::unordered_map<int, SPAIopOutputPSReference*>& iIdToOutputPSRef, std::unordered_map<int, std::pair<std::list<SPAIopInputPSInstance>, std::list<SPAIopOutputPSInstance*>>>& iRefIDToListOfAllInOutInst, SPAIopRep& iTokenRepType, bool& oIsShiftTranslationVectorApplied, double(&oShiftTranslation)[3]);
void CreateOutputProductStructureSkeletonRootInstance(SPAIopInputProductStructure& iInputPS, SPAIopPSImportResult &iInputPSResult, SPAIopOutputProductStructure& oOutputPS, std::unordered_map<int, SPAIopOutputPSReference*>& iIdToOutputPSRef, std::unordered_map<int, std::pair<std::list<SPAIopInputPSInstance>, std::list<SPAIopOutputPSInstance*>>>& iRefIDToListOfAllInOutInst);
void CreateOutputProductStructureSkeleton(SPAIopInputPSReference& iInputReference, SPAIopInputPSMissingFileIter& iMissingFiles, SPAIopOutputProductStructure& iOutputPS, SPAIopOutputPSReference& oOutputReference, std::unordered_map<int, SPAIopOutputPSReference*>& iIdToOutputPSRef, std::unordered_map<int, std::pair<std::list<SPAIopInputPSInstance>, std::list<SPAIopOutputPSInstance*>>>& iRefIDToListOfAllInOutInst);
int  DefinePSReference(SPAIopInputPSReference& iInputReference,SPAIopInputPSMissingFileIter& iMissingFiles, SPAIopOutputProductStructure& iOutputPS, const SPAIopPolicy& iPolicy, SPAIopOutputPSReference& oOutputReference, int& opNumDefined, std::unordered_map<int, std::pair<bool, double[3]>>& iRefIDToShiftTranslationMap, bool& oIsShiftTranslationVectorApplied, double(&oShiftTranslation)[3]);
bool ImportProductStructure(const wchar_t*& ipSourceFilePath, const wchar_t*& ipSourceDocType, const SPAIopPolicy& iPolicy, SPAIopInputProductStructure& oInputPS, SPAIopPSImportResult &oInputPSResult);
int ImportPart(SPAIopInputPSReference& iInputReference, const SPAIopPolicy& iPolicy, ENTITY_LIST& oAcisEntities, SPAIopPartImportResult& oPartImportResult);
void ImportVisu(SPAIopPartDocument& iAcisInteropPartDocument, const SPAIopPolicy& iPolicy, SPAIopVisuData& oVisuData, bool& oIsShiftTranslationVectorApplied, double(&oShiftTranslation)[3]);
size_t GetNonMissingParts(SPAIopInputProductStructure& iInputPSStructure, SPAIopPSImportResult &iInputPSResult);

//-----------------------------------------------------------------------------------------

static bool _g_runInMP = false;

class ProgressReporter;
ProgressReporter* _g_pProgressReporter = NULL;

// License file
#include "spatial_license.h"
#include "spa_unlock_result.hxx"
static bool _g_doingVisualization = false;
static bool _g_transferMiscData = false;
static bool _g_hasUserData = false;
static std::vector<ENTITY_LIST*> toBeReleased;

class UserProgressCallbackClass
{
private:

	size_t _count{ 0 };

public:

	void IncrementCount()
	{
		_count++;
	}

	size_t GetCount()
	{
		return _count;
	}
};

//-----------------------------------------------------------------------------------------

void PrintHelp()
{
	PrintMessage( L"Usage: IopAcisImportExport -i <full-input-file-path> -o <full-output-file-path> [-p <full-policy-file-path>] [-log <full-log-file-path>] [-itype <doctype>] [-otype <doctype>] [-v <value>] [-misc <data-transfer-mode>] [-progressCallbackMode <progress-callback-mode>] [-h] \n" );
	PrintMessage( L"\t-i  : The absolute path to the input file. \n" );
	PrintMessage( L"\t-o  : The absolute path to the output file. \n" );
	PrintMessage( L"\t-p  : The absolute path to the policy file used to control optional behavior during operations. \n" );
	PrintMessage( L"\t-log	: The absolute path to the log file. \n");
	PrintMessage( L"\t-itype  : The document type of the input file. \n");
	PrintMessage( L"\t-otype  : The document type of the output file. \n");
	PrintMessage( L"\t-t  : 1 to display elapsed time, (0 default). \n");
	PrintMessage( L"\t-v  : 1 to transfer visualization data, (0 default). \n");
	PrintMessage( L"\t-misc  : 1 to transfer the MiscData data, (0 default). \n" );
	PrintMessage( L"\t-mode  : MP to run in multi process mode.\n" );
	PrintMessage(L"\t-progressCallbackMode : 1 to use progress callback with user data, (0 default). \n");
	PrintMessage( L"\t-h  : This help.\n" );
}

//-----------------------------------------------------------------------------------------
class ProgressReporter 
{
public:

	//-----------------------------------------------------------------------------------------

	ProgressReporter() : _psDone(false), _numParts(0), nonMissingParts(0), _partsDone(0), _lastProgress(-1), _exportStarts(false) {}

	//-----------------------------------------------------------------------------------------

	void ProgressCallBack(double iProgress, bool& iAbort) 
	{
		int progressRange = 100;
		int percent = 0;

		double actualProgressPercent = iProgress * 100;
		int progress = static_cast<int>(actualProgressPercent);

		if (!_psDone || _exportStarts) 
		{
			percent = (int)((100.0*progress)/double(progressRange));
			if(_exportStarts)
				PrintMessage( L"Export, Output Product Structure with Parts, Progress : %d%%\n", percent);
			else
				PrintMessage( L"Import, Input Product Structure, Progress : %d%%\n", percent);
		} 
		else 
		{
			size_t cnt = _partsDone * progressRange + progress;
			percent = (int)((100.0*cnt)/(nonMissingParts*progressRange));
			if(percent > _lastProgress )
				PrintMessage( L"Import, Parts of Input Product Structure, Progress : %d%%\n", percent);
			_lastProgress = percent;
			if (progress == progressRange) 
				_partsDone += 1;
		}
	}

	//-----------------------------------------------------------------------------------------

	void ProgressCallBackWithUserData(double iProgress, bool& iAbort, void* ipUserData_dt)
	{
		UserProgressCallbackClass* pCallback = static_cast<UserProgressCallbackClass*>(ipUserData_dt);
		if(pCallback)
			pCallback->IncrementCount();

		int progressRange = 100;
		int percent = 0;
		double actualProgressPercent = iProgress * 100;
		int progress = static_cast<int>(actualProgressPercent);

		if (!_psDone || _exportStarts)
		{
			percent = (int)((100.0 * progress) / double(progressRange));
			if (_exportStarts)
				PrintMessage(L"Export, Output Product Structure with Parts, Progress : %d%%\n", percent);
			else
				PrintMessage(L"Import, Input Product Structure, Progress : %d%%\n", percent);
		}
		else
		{
			size_t cnt = _partsDone * progressRange + progress;
			percent = (int)((100.0 * cnt) / (nonMissingParts * progressRange));
			if (percent > _lastProgress)
				PrintMessage(L"Import, Parts of Input Product Structure, Progress : %d%%\n", percent);
			_lastProgress = percent;
			if (progress == progressRange)
				_partsDone += 1;
		}
	}

	//-----------------------------------------------------------------------------------------

	void SetNumParts(int iNumParts)
	{
		_numParts = iNumParts;
	}

	//-----------------------------------------------------------------------------------------

	void setNonMissingParts(size_t iNonMissingParts)
	{
		nonMissingParts = iNonMissingParts;
	}

	//-----------------------------------------------------------------------------------------

	void SetPsDone(bool isDone)
	{
		_psDone = isDone;
	}

	//-----------------------------------------------------------------------------------------

	void SetExportStarts(bool isStart)
	{
		_exportStarts = isStart;
		if (_exportStarts)
			PrintMessage(L"\n Exporting Product Structure\n");
	}

	//-----------------------------------------------------------------------------------------

private:

	//-----------------------------------------------------------------------------------------

	bool _psDone;
	size_t _numParts;
	size_t nonMissingParts;
	size_t _partsDone;
	int _lastProgress;
	bool _exportStarts;

	//-----------------------------------------------------------------------------------------

};

//-----------------------------------------------------------------------------------------

void MyProgressCallBack(double iProgress,bool& iAbort)
{
	if( _g_pProgressReporter != NULL )
	{
		_g_pProgressReporter->ProgressCallBack(iProgress, iAbort);
	}
}

//-----------------------------------------------------------------------------------------

void MyProgressCallBackWithUserData(double iProgress, bool& iAbort, void* ipUserData_dt)
{
	if (_g_pProgressReporter != NULL)
	{
		_g_pProgressReporter->ProgressCallBackWithUserData(iProgress, iAbort, ipUserData_dt);
	}
}

//-----------------------------------------------------------------------------------------

int ImportAndExportModel(const wchar_t*& ipSourceFilePath, const wchar_t*& ipTargetFilePath, const wchar_t*& ipSourceDocType, const wchar_t*& ipTargetDocType, const SPAIopPolicy& iImportPolicy, const SPAIopPolicy& iExportPolicy)
{
	int returnValue = SPAIopSampleResult_Import_Ok;

	_g_pProgressReporter = new ProgressReporter();
	// Import Input Product Structure 
	SPAIopInputProductStructure inputPS;
	SPAIopPSImportResult inputPSResult;
	bool gotPS = ImportProductStructure(ipSourceFilePath, ipSourceDocType, iImportPolicy, inputPS, inputPSResult);

	if (!gotPS) 
	{
		// If the product structure can't be loaded there's no way to translate anything
		return SPAIopSampleResult_Import_PS_Fail;
	}

	// Traverse Input Product Structure and create output product structure:
	// Create a output reference for each reference, and for each unique ID, import the contents of the file to a container
	// Set the corresponding output references to the corresponding containers in memory

	SPAIopOutputProductStructure outputPS;
	SPAIopWString wTargetDocType(ipTargetDocType);
	outputPS.SetDocumentType(wTargetDocType);
	int numCopiedInstances = 0;
	returnValue = DefineOutputProductStructure(inputPS, inputPSResult, iImportPolicy, outputPS,numCopiedInstances);
	
	if (_g_doingVisualization)
		numCopiedInstances += TransferAssemblyVisualization( inputPS, outputPS );

	std::wstring targetFilePath(ipTargetFilePath);
	SPAIopWString wTargetFilePath( targetFilePath.c_str());

	if (nullptr != _g_pProgressReporter)
		_g_pProgressReporter->SetExportStarts(true);

	if (numCopiedInstances > 0) 
	{
		SPAIopProductStructureExporter exporter;
		try 
		{
			UserProgressCallbackClass localCallackClass;
			if (_g_hasUserData)
				exporter.SetProgressCallBackWithUserData((SPAIopProgressCallbackWithUserData)MyProgressCallBackWithUserData, &localCallackClass);
			else
				exporter.SetProgressCallBack((SPAIopProgressCallback)MyProgressCallBack);
			SPAIopResult res = exporter.ExportWithPolicy(iExportPolicy, outputPS, wTargetFilePath);

			if (_g_hasUserData)
			{
				size_t count = localCallackClass.GetCount();
				PrintMessage(L"\n\nUser data count, after ExportWithPolicy, is :%zd\n", count);
			}
		}
		catch ( SPAIopAcisError& error )
		{
			DisplayError( error );
			returnValue = SPAIopSampleResult_Export_PS_Fail;
		}
		catch ( ... )
		{
			std::wcout << "FAILURE: Unknown exception caught! " << std::endl;
			returnValue = SPAIopSampleResult_Export_PS_Fail;
		}
	}
	else 
	{
		// Nothing was imported, so create an 'empty' file.
		// outputPS can potentially have invalid, partially references added to it,
		// so start from scratch with emptyPS
		SPAIopOutputProductStructure emptyPS;
		emptyPS.SetProductName( inputPS.GetProductName() );

		SPAIopOutputPSReference iOutRef(emptyPS, 42);
		SPAIopWString refName(L"");
		iOutRef.SetName(refName);

		ENTITY_LIST* entities = ACIS_NEW ENTITY_LIST;
		SPAIopAcisOutputPart partData;
		partData.SetPartEntities(*entities);

		iOutRef.SetOutputPartData( partData);
		
		toBeReleased.push_back(entities);
		iOutRef.SetDefined(true);

		SPAIopOutputPSInstance iOutInst(iOutRef);
		SPAIopWString instName(L"");
		iOutInst.SetName(instName);

		double affineTransform[3][3] = {{1.0,0.0,0.0},
		{0.0,1.0,0.0},
		{0.0,0.0,1.0}};

		double translation[3] = {0.0, 0.0, 0.0};
		iOutInst.SetInstancePositionMatrix(affineTransform, translation);
		//iOutRef.AddInstance(iOutInst);

		emptyPS.AddToRoot(iOutInst);
		SPAIopProductStructureExporter exporter;
		try 
		{
			UserProgressCallbackClass localCallackClass;
			if (_g_hasUserData)
				exporter.SetProgressCallBackWithUserData((SPAIopProgressCallbackWithUserData)MyProgressCallBackWithUserData, &localCallackClass);
			else
				exporter.SetProgressCallBack((SPAIopProgressCallback)MyProgressCallBack);
			SPAIopResult res = exporter.ExportWithPolicy(iExportPolicy, emptyPS, wTargetFilePath);
			numCopiedInstances++;

			if (_g_hasUserData)
			{
				size_t count = localCallackClass.GetCount();
				PrintMessage(L"\n\nUser data count, after ExportWithPolicy, is :%zd\n", count);
			}
		}
		catch ( SPAIopAcisError& fatalErr )
		{
			DisplayError( fatalErr );
			returnValue = SPAIopSampleResult_Export_PS_Fail;
		}
		catch ( ... )
		{
			std::wcout << "FAILURE: Unknown exception caught! " << std::endl;
			returnValue = SPAIopSampleResult_Export_PS_Fail;
		}
	}

	delete _g_pProgressReporter;
	_g_pProgressReporter = NULL;

	return returnValue;
}

//-----------------------------------------------------------------------------------------
int DefineOutputProductStructureMP(SPAIopInputProductStructure& iInputPS, SPAIopPSImportResult &iInputPSResult, const SPAIopPolicy& iPolicy, SPAIopOutputProductStructure& oOutputPS, int& opCopiedInstances)
{
	int result = SPAIopSampleResult_Import_Ok;
	int copiedInstances = 0;
	SPAIopInputPSMissingFileIter missingFiles = iInputPSResult.GetMissingFileIterator();
	SPAIopMultiProcessBatchDefinition batchDef(iInputPS, iPolicy);
	if (_g_doingVisualization)
		batchDef.AddType(SPAIopRep::SPAIopRep_All);
	else
		batchDef.AddType(SPAIopRep::SPAIopRep_Mechanical);
	SPAIopMultiProcessSpoolerEx spooler(batchDef);
	bool didStart = spooler.InitiateConversion();

	// Loop until all done
	if (!didStart)
	{
		std::wcerr << "ERROR: The conversion was not initiated in parallel" << std::endl;
		return -1;
	}

	std::unordered_map<int, SPAIopInputPSInstance> rootInstanceIDs;
	int nbInstances = iInputPS.GetRootInstancesCount();
	for (int i = 0; i < nbInstances; i++)
	{
		SPAIopInputPSInstance inputRootInstance = iInputPS.GetRootInstance(i);
		SPAIopInputPSReference inputReference = inputRootInstance.GetReference();
		unsigned int refID = inputReference.GetUniqueID();
		rootInstanceIDs[refID] = inputRootInstance;
	}

	std::unordered_map<int, SPAIopOutputPSReference*> idToOutputPSRef;
	std::unordered_map<int, std::pair<std::list<SPAIopInputPSInstance>, std::list<SPAIopOutputPSInstance*>>> refIDToListOfAllInOutInst;
	CreateOutputProductStructureSkeletonRootInstance(iInputPS, iInputPSResult, oOutputPS, idToOutputPSRef, refIDToListOfAllInOutInst);


	SPAIopMultiProcessBatchDefinition::Token_t refToken;
	while (spooler.WaitForNextAvailableReference(refToken) != SPAIopMultiProcessSpoolerEx::WaitResult_t::NoMoreData)
	{
		unsigned int refID = refToken.GetRefID();
		SPAIopRep tokenRepType = refToken.GetImportedRepresentation();
		if (rootInstanceIDs.find(refID) != rootInstanceIDs.end())
		{
			SPAIopInputPSInstance inputRootInstance = rootInstanceIDs[refID];

			bool skipInstance = ShouldInstanceSkipped(inputRootInstance);
			if (true == skipInstance)
			{
				continue;
			}

			SPAIopInputPSReference inputReference = inputRootInstance.GetReference();
			SPAIopWString referenceStorageName = inputReference.GetStorageName();
			bool isMissingReference = missingFiles.ContainsPath(referenceStorageName);
			if (false == isMissingReference)
			{
				unsigned int refID = inputReference.GetUniqueID();
				SPAIopOutputPSReference outputReference = *(idToOutputPSRef[refID]);

				int numDefined = 0;
				bool isShiftTranslationVectorApplied = false;
				double shiftTranslationVector[3] = { 0.0 };
				int res = DefinePSReferenceMP(inputReference, missingFiles, oOutputPS, iPolicy, outputReference, numDefined, idToOutputPSRef, refIDToListOfAllInOutInst, tokenRepType, isShiftTranslationVectorApplied, shiftTranslationVector);
				if (0 != res)
					result = res;
				if (numDefined > 0)
				{
					copiedInstances += numDefined;
					if (isShiftTranslationVectorApplied)
					{
						const auto unorderedMapIterator = refIDToListOfAllInOutInst.find(refID);
						if (unorderedMapIterator != refIDToListOfAllInOutInst.end())
						{
							//Applying shift translation to all the instances having current reference/RefID
							//we don't process previously defined refs again
							while (!(unorderedMapIterator->second.first.empty()))
							{
								SPAIopInputPSInstance inputInstance = unorderedMapIterator->second.first.front();
								SPAIopOutputPSInstance outputPSInstance = *(unorderedMapIterator->second.second.front());
								ModifyInstanceTransform(inputInstance, shiftTranslationVector, outputPSInstance);
								unorderedMapIterator->second.first.pop_front();
								delete (unorderedMapIterator->second.second.front());
								unorderedMapIterator->second.second.pop_front();
							}
						}
					}
				}
			}
		}
		else
		{
			SPAIopPolicy policy = refToken.GetPolicy();
			SPAIopRep availableType = refToken.GetImportedRepresentation();

			SPAIopInputPSReference reference = iInputPS.GetReferenceFromId(refID);
			SPAIopWString format = reference.GetDefinitionDocumentType();

			SPAIopWString referenceStorageName = reference.GetStorageName();
			bool isMissingReference = missingFiles.ContainsPath(referenceStorageName);
			if (false == isMissingReference)
			{
				unsigned int refID = reference.GetUniqueID();
				SPAIopOutputPSReference outputReference = *(idToOutputPSRef[refID]);

				int numDefined = 0;
				bool isShiftTranslationVectorApplied = false;
				double shiftTranslationVector[3] = { 0.0 };
				int res = DefinePSReferenceMP(reference, missingFiles, oOutputPS, iPolicy, outputReference, numDefined, idToOutputPSRef, refIDToListOfAllInOutInst, tokenRepType, isShiftTranslationVectorApplied, shiftTranslationVector);
				if (0 != res)
					result = res;
				if (numDefined > 0)
				{
					copiedInstances += numDefined;
					if (isShiftTranslationVectorApplied)
					{
						const auto unorderedMapIterator = refIDToListOfAllInOutInst.find(refID);
						if (unorderedMapIterator != refIDToListOfAllInOutInst.end())
						{
							//Applying shift translation to all the instances having current reference/RefID
							//we don't process previously defined refs again
							while (!(unorderedMapIterator->second.first.empty()))
							{
								SPAIopInputPSInstance inputInstance = unorderedMapIterator->second.first.front();
								SPAIopOutputPSInstance outputPSInstance = *(unorderedMapIterator->second.second.front());
								ModifyInstanceTransform(inputInstance, shiftTranslationVector, outputPSInstance);
								unorderedMapIterator->second.first.pop_front();
								delete (unorderedMapIterator->second.second.front());
								unorderedMapIterator->second.second.pop_front();
							}
						}
					}
				}
			}
		}
	}
	opCopiedInstances = copiedInstances;

	//Clearing maps to avoid memory leak
	for (auto unorderedMapIterator = refIDToListOfAllInOutInst.begin(); unorderedMapIterator != refIDToListOfAllInOutInst.end();)
	{
		for (auto listIterator = unorderedMapIterator->second.first.begin(); listIterator != unorderedMapIterator->second.first.end();)
		{
			listIterator = unorderedMapIterator->second.first.erase(listIterator);
		}

		for (auto listIterator = unorderedMapIterator->second.second.begin(); listIterator != unorderedMapIterator->second.second.end();)
		{
			delete* listIterator;
			listIterator = unorderedMapIterator->second.second.erase(listIterator);
		}

		if (unorderedMapIterator->second.first.empty() && unorderedMapIterator->second.second.empty())
		{
			unorderedMapIterator = refIDToListOfAllInOutInst.erase(unorderedMapIterator);
		}
		else
			++unorderedMapIterator;
	}
	refIDToListOfAllInOutInst.clear();

	for (auto unorderedMapIterator = idToOutputPSRef.begin(); unorderedMapIterator != idToOutputPSRef.end(); ++unorderedMapIterator)
	{
		delete unorderedMapIterator->second;
	}
	idToOutputPSRef.clear();

	return result;
}



void CreateOutputProductStructureSkeletonRootInstance(SPAIopInputProductStructure& iInputPS, SPAIopPSImportResult &iInputPSResult, SPAIopOutputProductStructure& oOutputPS, std::unordered_map<int, SPAIopOutputPSReference*>& iIdToOutputPSRef, std::unordered_map<int, std::pair<std::list<SPAIopInputPSInstance>, std::list<SPAIopOutputPSInstance*>>>& iRefIDToListOfAllInOutInst)
{
	SPAIopInputPSMissingFileIter missingFiles = iInputPSResult.GetMissingFileIterator();
	int nbInstances = iInputPS.GetRootInstancesCount();
	int i = 0;
	for (i = 0; i < nbInstances; i++)
	{
		// Get Input PS info (instance, reference, ID)
		SPAIopInputPSInstance inputRootInstance = iInputPS.GetRootInstance(i);

		bool skipInstance = ShouldInstanceSkipped(inputRootInstance);
		if (true == skipInstance)
		{
			continue;
		}

		SPAIopInputPSReference inputReference = inputRootInstance.GetReference();
		SPAIopWString referenceStorageName = inputReference.GetStorageName();
		bool isMissingReference = missingFiles.ContainsPath(referenceStorageName);
		if (false == isMissingReference)
		{
			unsigned int refID = inputReference.GetUniqueID();

			// Replicate the same on outPS
			SPAIopOutputPSReference* outputReference = NULL;

			//If RefID entry already present in map get that reference
			if (iIdToOutputPSRef.find(refID) != iIdToOutputPSRef.end())
				outputReference = iIdToOutputPSRef[refID];
			else
				outputReference = new SPAIopOutputPSReference(oOutputPS, refID); // refID is unique and used to identify the reference

			iIdToOutputPSRef.emplace(refID, outputReference);
			SPAIopOutputPSInstance* outputInstance = new SPAIopOutputPSInstance(*outputReference);
			DefinePSInstance(inputRootInstance, *outputInstance);
			iRefIDToListOfAllInOutInst[refID].first.push_back(inputRootInstance);
			iRefIDToListOfAllInOutInst[refID].second.push_back(outputInstance);

			oOutputPS.AddToRoot(*outputInstance);
			CreateOutputProductStructureSkeleton(inputReference, missingFiles, oOutputPS, *outputReference, iIdToOutputPSRef, iRefIDToListOfAllInOutInst);
		}
	}
}


void CreateOutputProductStructureSkeleton(SPAIopInputPSReference& iInputReference, SPAIopInputPSMissingFileIter& iMissingFiles, SPAIopOutputProductStructure& iOutputPS, SPAIopOutputPSReference& oOutputReference, std::unordered_map<int, SPAIopOutputPSReference*>& iIdToOutputPSRef, std::unordered_map<int, std::pair<std::list<SPAIopInputPSInstance>, std::list<SPAIopOutputPSInstance*>>>& iRefIDToListOfAllInOutInst)
{
	if (!oOutputReference.IsDefined())
	{
		if (!iInputReference.IsPart())
		{
			SPAIopWString referencePID;
			bool hasPID = iInputReference.GetPID(referencePID);
			if (hasPID)
			{
				const wchar_t* refName = NULL;
				referencePID.ToWChar(refName);
				SPAIopOutputPSReference* pReference = &oOutputReference;
				TrackReferences(add_subassembly, refName, pReference);
			}

			int nbInstances = iInputReference.GetInstancesCount();
			if (nbInstances > 0)
			{
				int i = 0;
				for (i = 0; i < nbInstances; i++)
				{
					// Get Input Product Structure info (instance, reference, ID)
					SPAIopInputPSInstance iNodeInstance = iInputReference.GetInstance(i);

					bool skipInstance = ShouldInstanceSkipped(iNodeInstance);
					if (true == skipInstance)
					{
						continue;
					}

					SPAIopInputPSReference iNodeReference = iNodeInstance.GetReference();
					SPAIopWString referenceStorageName = iNodeReference.GetStorageName();
					bool isMissingReference = iMissingFiles.ContainsPath(referenceStorageName);
					if (false == isMissingReference)
					{
						unsigned int refID = iNodeReference.GetUniqueID();

						// Replicate the same on outPS
						SPAIopOutputPSReference* newOutputReference = NULL;

						//If RefID entry already present in map get that reference
						if (iIdToOutputPSRef.find(refID) != iIdToOutputPSRef.end())
							newOutputReference = iIdToOutputPSRef[refID];
						else
							newOutputReference = new SPAIopOutputPSReference(iOutputPS, refID); // refID is unique and used to identify the reference

						iIdToOutputPSRef.emplace(refID, newOutputReference);
						SPAIopOutputPSInstance* iOutputInstance = new SPAIopOutputPSInstance(*newOutputReference);
						DefinePSInstance(iNodeInstance, *iOutputInstance);
						iRefIDToListOfAllInOutInst[refID].first.push_back(iNodeInstance);
						iRefIDToListOfAllInOutInst[refID].second.push_back(iOutputInstance);

						oOutputReference.AddInstance(*iOutputInstance);
						CreateOutputProductStructureSkeleton(iNodeReference, iMissingFiles, iOutputPS, *newOutputReference, iIdToOutputPSRef, iRefIDToListOfAllInOutInst);
					}
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------------------


int DefineOutputProductStructure(SPAIopInputProductStructure& iInputPS, SPAIopPSImportResult &iInputPSResult, const SPAIopPolicy& iPolicy, SPAIopOutputProductStructure& oOutputPS, int& opCopiedInstances)
{
	// Navigate all of the nodes of the input product structure and create the corresponding output product
	// structure (SPAIopOutputProductStructure) and its out instances (SPAIopOutputPSInstance) and output references (SPAIopOutputPSReference)

	bool success = true;
	int result = SPAIopSampleResult_Import_Ok;
	SPAIopInputPSMissingFileIter missingFiles = iInputPSResult.GetMissingFileIterator();

	SPAIopWString productName = iInputPS.GetProductName();
	oOutputPS.SetProductName( productName );
	if( _g_transferMiscData)
	{
		SPAIopPSReferenceMiscData rootMiscData = iInputPS.GetMiscData();

		SPAIopMiscOutputAsmData refMiscData;
		TransferAssemblyReferenceMiscData( rootMiscData, refMiscData);
		oOutputPS.SetRootMiscData( refMiscData);
	}

	SPAIopWString productPID;
	bool hasPID = iInputPS.GetPID(productPID);
	if(hasPID)
	{
		const wchar_t* prodName = NULL;
		productPID.ToWChar( prodName );
		SPAIopOutputPSReference* noRef = NULL;
		TrackReferences (set_root, prodName, noRef);
	}

	if (_g_runInMP)
	{
		return DefineOutputProductStructureMP(iInputPS, iInputPSResult, iPolicy, oOutputPS, opCopiedInstances);
	}

	//Multiple instances having same reference will need below map to store shift translation vector for each unique reference
	//This is done as we don't process already defined/processed reference again
	std::unordered_map<int, std::pair<bool, double[3]>> refIDToShiftTranslationMap;
	SPAIopWorldOrientation orientation = SPAIopWorldOrientation_Unknown;
	iInputPS.GetWorldOrientation(orientation);
	oOutputPS.SetWorldOrientation(orientation);


	int nbInstances = iInputPS.GetRootInstancesCount();
	int copiedInstances = 0;
	for( int i = 0; i < nbInstances && true == success; i++ )
	{
		// Get Input PS info (instance, reference, ID)
		SPAIopInputPSInstance inputRootInstance = iInputPS.GetRootInstance(i);

		bool skipInstance = ShouldInstanceSkipped( inputRootInstance );
		if( true == skipInstance )
		{
			continue;
		}

		SPAIopInputPSReference inputReference = inputRootInstance.GetReference();
		SPAIopWString referenceStorageName = inputReference.GetStorageName();
		bool isMissingReference = missingFiles.ContainsPath( referenceStorageName );
		if ( false == isMissingReference )
		{
			unsigned int refID = inputReference.GetUniqueID();

			// Replicate the same on outPS
			SPAIopOutputPSReference outputReference(oOutputPS, refID); // refID is unique and used to identify the reference

			int numDefined = 0;
			bool isShiftTranslationVectorApplied = false;
			double shiftTranslationVector[3] = { 0.0 };
			int res = DefinePSReference(inputReference, missingFiles, oOutputPS, iPolicy, outputReference, numDefined, refIDToShiftTranslationMap, isShiftTranslationVectorApplied, shiftTranslationVector);
			if (0 != res)
				result = res;
			if( numDefined > 0 )
			{
				copiedInstances += numDefined;

				SPAIopOutputPSInstance outputInstance(outputReference);
				DefinePSInstance(inputRootInstance, outputInstance);

				if (isShiftTranslationVectorApplied)
				{
					ModifyInstanceTransform(inputRootInstance, shiftTranslationVector, outputInstance);
					refIDToShiftTranslationMap[refID].first = isShiftTranslationVectorApplied;
					std::copy(std::begin(shiftTranslationVector), std::end(shiftTranslationVector), std::begin(refIDToShiftTranslationMap[refID].second));
				}
				else
				{
					//Case where reference is already defined but have shift translation vector
					const auto unorderedMapIterator = refIDToShiftTranslationMap.find(refID);
					if (unorderedMapIterator != refIDToShiftTranslationMap.end())
					{
						isShiftTranslationVectorApplied = unorderedMapIterator->second.first;
						if (isShiftTranslationVectorApplied)
						{
							shiftTranslationVector[0] = unorderedMapIterator->second.second[0];
							shiftTranslationVector[1] = unorderedMapIterator->second.second[1];
							shiftTranslationVector[2] = unorderedMapIterator->second.second[2];
							ModifyInstanceTransform(inputRootInstance, shiftTranslationVector, outputInstance);
						}
					}
				}

				oOutputPS.AddToRoot(outputInstance);
			}
		}
	}
	opCopiedInstances = copiedInstances;

	refIDToShiftTranslationMap.clear();

	return result;
}

int DefinePSReferenceMP(SPAIopInputPSReference& iInputReference, SPAIopInputPSMissingFileIter& iMissingFiles, SPAIopOutputProductStructure& iOutputPS, const SPAIopPolicy& iPolicy, SPAIopOutputPSReference& oOutputReference, int& opNumDefined, std::unordered_map<int, SPAIopOutputPSReference*>& iIdToOutputPSRef, std::unordered_map<int, std::pair<std::list<SPAIopInputPSInstance>, std::list<SPAIopOutputPSInstance*>>>& iRefIDToListOfAllInOutInst, SPAIopRep& iTokenRepType, bool& oIsShiftTranslationVectorApplied, double(&oShiftTranslation)[3])
{
	int numDefined = 0;
	int result = SPAIopSampleResult_Import_Ok;
	if (!oOutputReference.IsDefined())
	{
		if (iInputReference.IsPart())    // part node
		{
			// import the document
			ENTITY_LIST* iopAcisEntities = ACIS_NEW ENTITY_LIST;
			SPAIopPartImportResult partImportResult;
			if (iTokenRepType == SPAIopRep::SPAIopRep_All || iTokenRepType == SPAIopRep::SPAIopRep_Mechanical)
				result = ImportPart(iInputReference, iPolicy, *iopAcisEntities, partImportResult);

			bool addedDataToPartRef = false;
			if (iopAcisEntities && iopAcisEntities->count() > 0)
			{
				SPAIopAcisOutputPart partData;
				partData.SetPartEntities(*iopAcisEntities);

				oIsShiftTranslationVectorApplied = partImportResult.GetShiftTranslation(oShiftTranslation[0], oShiftTranslation[1], oShiftTranslation[2]);
				if (oIsShiftTranslationVectorApplied)
				{
					PrintMessage(L"\nGeometry is shifted near the origin to fit it inside the current modeler range, Following Shift Translation Vector should be applied on the part to get the original input coordinates of geometry:- X: %lf\tY: %lf\tZ: %lf\n", oShiftTranslation[0], oShiftTranslation[1], oShiftTranslation[2]);
				}

				const wchar_t* pNewTargetUnit = NULL;
				SPAIopAcisPartImportResult* acisPartImportResult = static_cast<SPAIopAcisPartImportResult*>(&partImportResult);
				if (NULL != acisPartImportResult)
				{
					const SPAIopLengthUnitType newTargetUnit = acisPartImportResult->GetUnit();
					if (SPAIopLengthUnit_Unknown != newTargetUnit)
					{
						SPAIopWString unitStr = SPAIopLengthUnitUtils::LengthUnitToString(newTargetUnit);
						const wchar_t* pUnitArray = NULL;
						unitStr.ToWChar(pUnitArray);
						PrintMessage(L"\nTarget Unit Modified to fit the geometry inside current modeler range, New Target Unit applied on the part:- %ls\n", pUnitArray);
						//Setting modified target unit on output part
						partData.SetPartUnit(newTargetUnit);

						//Converting shift translation vector to product structure unit if target unit is modified, so that new transform gets calculated correctly
						if (oIsShiftTranslationVectorApplied)
						{
							double unitScale = 1.0;
							SPAIopLengthUnitType productStructureUnit = SPAIopLengthUnit_Unknown;
							SPAIopModelerOptionsAcis acisModelerOptions;
							const bool res = iPolicy.GetCurrentModelerOptions(acisModelerOptions);
							if (res)
							{
								const bool foundOption = acisModelerOptions.GetCurrentModelerUnits(productStructureUnit);
								if (!foundOption)
									productStructureUnit = SPAIopLengthUnit_Millimeter;
							}
							else
								productStructureUnit = SPAIopLengthUnit_Millimeter;

							unitScale = SPAIopLengthUnitUtils::GetScalingFactor(newTargetUnit, productStructureUnit);
							oShiftTranslation[0] = oShiftTranslation[0] * unitScale;
							oShiftTranslation[1] = oShiftTranslation[1] * unitScale;
							oShiftTranslation[2] = oShiftTranslation[2] * unitScale;
						}
					}
				}

				if (_g_transferMiscData)
					TransferPartReferenceMiscData(iInputReference, partImportResult, partData);

				oOutputReference.SetOutputPartData(partData);

				if (_g_doingVisualization)
				{
					SPAIopPMIData pmiData = partImportResult.GetPMIData();
					partData.SetPMIData(pmiData);
					SPAIopMappingData mappingData = partImportResult.GetMappingData();
					partData.SetMappingData(mappingData);
				}

				toBeReleased.push_back(iopAcisEntities);
				addedDataToPartRef = true;
			}
			else
			{
				PrintMessage(L"No Entities\n");
			}
			SPAIopPartDocument refPartDocument = iInputReference.GetDocument();
			// import the visualization, if requested, and directly attach it to the outputPS
			if (_g_doingVisualization && (iTokenRepType == SPAIopRep::SPAIopRep_All || iTokenRepType == SPAIopRep::SPAIopRep_Visualization))
			{
				SPAIopVisuData iopVisuData;
				ImportVisu(refPartDocument, iPolicy, iopVisuData, oIsShiftTranslationVectorApplied, oShiftTranslation);
				oOutputReference.SetVisualization(iopVisuData);
				addedDataToPartRef = true;
			}

			if (true == addedDataToPartRef)
			{
				numDefined += 1;
			}
		}
		else   // sub-assembly node
		{
			SPAIopWString referencePID;
			bool hasPID = iInputReference.GetPID(referencePID);
			if (hasPID)
			{
				const wchar_t* refName = NULL;
				referencePID.ToWChar(refName);
				SPAIopOutputPSReference* pReference = &oOutputReference;
				TrackReferences(add_subassembly, refName, pReference);
			}

			int nbInstances = iInputReference.GetInstancesCount();
			if (nbInstances > 0)
			{
				int i = 0;
				for (i = 0; i < nbInstances; i++)
				{
					// Get Input Product Structure info (instance, reference, ID)
					SPAIopInputPSInstance iNodeInstance = iInputReference.GetInstance(i);

					bool skipInstance = ShouldInstanceSkipped(iNodeInstance);
					if (true == skipInstance)
					{
						continue;
					}

					SPAIopInputPSReference iNodeReference = iNodeInstance.GetReference();
					SPAIopWString referenceStorageName = iNodeReference.GetStorageName();
					bool isMissingReference = iMissingFiles.ContainsPath(referenceStorageName);
					if (false == isMissingReference)
					{
						unsigned int refID = iNodeReference.GetUniqueID();
						SPAIopOutputPSReference newOutputReference = *(iIdToOutputPSRef[refID]);
						int entities = 0;
						bool isShiftTranslationVectorApplied = false;
						double shiftTranslationVector[3] = { 0.0 };
						int res = DefinePSReferenceMP(iNodeReference, iMissingFiles, iOutputPS, iPolicy, newOutputReference, entities, iIdToOutputPSRef, iRefIDToListOfAllInOutInst, iTokenRepType, isShiftTranslationVectorApplied, shiftTranslationVector);
						if (0 != res)
							result = res;
						if (entities > 0)
						{
							numDefined += entities;
							if (isShiftTranslationVectorApplied)
							{
								const auto unorderedMapIterator = iRefIDToListOfAllInOutInst.find(refID);
								if (unorderedMapIterator != iRefIDToListOfAllInOutInst.end())
								{
									//Applying shift translation to all the instances having current reference/RefID
									//we don't process previously defined refs again
									while (!(unorderedMapIterator->second.first.empty()))
									{
										SPAIopInputPSInstance inputInstance = unorderedMapIterator->second.first.front();
										SPAIopOutputPSInstance outputPSInstance = *(unorderedMapIterator->second.second.front());
										ModifyInstanceTransform(inputInstance, shiftTranslationVector, outputPSInstance);
										unorderedMapIterator->second.first.pop_front();
										delete (unorderedMapIterator->second.second.front());
										unorderedMapIterator->second.second.pop_front();
									}
								}
							}
						}
					}
				}
			}
			if (_g_transferMiscData)
			{
				SPAIopPSReferenceMiscData asmReferenceMiscData = iInputReference.GetMiscData();
				SPAIopMiscOutputAsmData refOutputMiscData;
				TransferAssemblyReferenceMiscData(asmReferenceMiscData, refOutputMiscData);
				oOutputReference.SetOutputSubAsmLevelMiscData(refOutputMiscData);
			}
		}

		// In some formats supporting delayed part loading, we do not get reference name without loading (importing) part first. So set it after part is imported.
		SPAIopWString referenceName = iInputReference.GetReferenceName();
		oOutputReference.SetName(referenceName);

		// mark this reference as fully defined so we don't go through this code again if it is used by another instance
		if (numDefined > 0)
		{
			oOutputReference.SetDefined(true);
		}
	}
	else
	{
		// Don't do anything for previously defined refs, but pretend they were defined this time
		numDefined = 1;
	}
	opNumDefined = numDefined;
	return result;
}

//-----------------------------------------------------------------------------------------

int DefinePSReference(SPAIopInputPSReference& iInputReference, SPAIopInputPSMissingFileIter& iMissingFiles, SPAIopOutputProductStructure& iOutputPS, const SPAIopPolicy& iPolicy, SPAIopOutputPSReference& oOutputReference, int& opNumDefined, std::unordered_map<int, std::pair<bool, double[3]>>& iRefIDToShiftTranslationMap, bool& oIsShiftTranslationVectorApplied, double(&oShiftTranslation)[3])
{
	int numDefined = 0;
	int result = SPAIopSampleResult_Import_Ok;
	if( !oOutputReference.IsDefined() )
	{
		if( iInputReference.IsPart() )    // part node
		{
			// import the document
			ENTITY_LIST* iopAcisEntities = ACIS_NEW ENTITY_LIST;
			SPAIopPartImportResult partImportResult;
			result = ImportPart(iInputReference, iPolicy, *iopAcisEntities, partImportResult);

			bool addedDataToPartRef = false;
			if (iopAcisEntities && iopAcisEntities->count() > 0)
			{
				SPAIopAcisOutputPart partData;
				partData.SetPartEntities(*iopAcisEntities);

				oIsShiftTranslationVectorApplied = partImportResult.GetShiftTranslation(oShiftTranslation[0], oShiftTranslation[1], oShiftTranslation[2]);
				if (oIsShiftTranslationVectorApplied)
				{
					PrintMessage(L"\nGeometry is shifted near the origin to fit it inside the current modeler range, Following Shift Translation Vector should be applied on the part to get the original input coordinates of geometry:- X: %lf\tY: %lf\tZ: %lf\n", oShiftTranslation[0], oShiftTranslation[1], oShiftTranslation[2]);
				}

				const wchar_t* pNewTargetUnit = NULL;
				SPAIopAcisPartImportResult* acisPartImportResult = static_cast<SPAIopAcisPartImportResult*>(&partImportResult);
				if (NULL != acisPartImportResult)
				{
					const SPAIopLengthUnitType newTargetUnit = acisPartImportResult->GetUnit();
					if (SPAIopLengthUnit_Unknown != newTargetUnit)
					{
						SPAIopWString unitStr = SPAIopLengthUnitUtils::LengthUnitToString(newTargetUnit);
						const wchar_t* pUnitArray = NULL;
						unitStr.ToWChar(pUnitArray);
						PrintMessage(L"\nTarget Unit Modified to fit the geometry inside current modeler range, New Target Unit applied on the part:- %ls\n", pUnitArray);
						//Setting modified target unit on output part
						partData.SetPartUnit(newTargetUnit);

						//Converting shift translation vector to product structure unit if target unit is modified, so that new transform gets calculated correctly
						if (oIsShiftTranslationVectorApplied)
						{
							double unitScale = 1.0;
							SPAIopLengthUnitType productStructureUnit = SPAIopLengthUnit_Unknown;
							SPAIopModelerOptionsAcis acisModelerOptions;
							const bool res = iPolicy.GetCurrentModelerOptions(acisModelerOptions);
							if (res)
							{
								const bool foundOption = acisModelerOptions.GetCurrentModelerUnits(productStructureUnit);
								if (!foundOption)
									productStructureUnit = SPAIopLengthUnit_Millimeter;
							}
							else
								productStructureUnit = SPAIopLengthUnit_Millimeter;

							unitScale = SPAIopLengthUnitUtils::GetScalingFactor(newTargetUnit, productStructureUnit);
							oShiftTranslation[0] = oShiftTranslation[0] * unitScale;
							oShiftTranslation[1] = oShiftTranslation[1] * unitScale;
							oShiftTranslation[2] = oShiftTranslation[2] * unitScale;
						}
					}
				}
				
				if( _g_transferMiscData)
					TransferPartReferenceMiscData(iInputReference, partImportResult, partData);

				oOutputReference.SetOutputPartData( partData);

				if (_g_doingVisualization)
				{
					SPAIopPMIData pmiData = partImportResult.GetPMIData();
					partData.SetPMIData(pmiData);
					SPAIopMappingData mappingData = partImportResult.GetMappingData();
					partData.SetMappingData(mappingData);
				}

				toBeReleased.push_back(iopAcisEntities);
				addedDataToPartRef = true;
			}
			else
			{
				PrintMessage(L"No Entities\n");
			}
			SPAIopPartDocument refPartDocument = iInputReference.GetDocument();
			// import the visualization, if requested, and directly attach it to the outputPS
			if (_g_doingVisualization)
			{
				SPAIopVisuData iopVisuData;
				ImportVisu( refPartDocument, iPolicy, iopVisuData, oIsShiftTranslationVectorApplied, oShiftTranslation);
				oOutputReference.SetVisualization(iopVisuData);
				addedDataToPartRef = true;
			}

			if (true == addedDataToPartRef)
			{
				numDefined += 1;
			}
		}
		else   // sub-assembly node
		{
			SPAIopWString referencePID;
			bool hasPID = iInputReference.GetPID(referencePID);
			if(hasPID)
			{
				const wchar_t* refName = NULL;
				referencePID.ToWChar( refName );
				SPAIopOutputPSReference* pReference = &oOutputReference;
				TrackReferences (add_subassembly, refName, pReference);
			}
;
			int nbInstances=iInputReference.GetInstancesCount();
			if (nbInstances > 0) {
				for( int i = 0; i < nbInstances; i++ )
				{
					// Get Input Product Structure info (instance, reference, ID)
					SPAIopInputPSInstance iNodeInstance = iInputReference.GetInstance(i);

					bool skipInstance = ShouldInstanceSkipped( iNodeInstance );
					if( true == skipInstance )
					{
						continue;
					}

					SPAIopInputPSReference iNodeReference = iNodeInstance.GetReference();
					SPAIopWString referenceStorageName = iNodeReference.GetStorageName();
					bool isMissingReference = iMissingFiles.ContainsPath( referenceStorageName );
					if ( false == isMissingReference )
					{
						unsigned int refID = iNodeReference.GetUniqueID();

						// Replicate the same for the output product structure
						SPAIopOutputPSReference newOutputReference(iOutputPS, refID);
						int entities = 0;
						bool isShiftTranslationVectorApplied = false;
						double shiftTranslationVector[3] = { 0.0 };
						int res = DefinePSReference(iNodeReference, iMissingFiles, iOutputPS, iPolicy, newOutputReference, entities, iRefIDToShiftTranslationMap, isShiftTranslationVectorApplied, shiftTranslationVector);// Recursively navigate nodes below this sub-assembly node
						if (0 != res)
							result = res;
						if( entities > 0 )
						{
							SPAIopOutputPSInstance iOutputInstance(newOutputReference);
							DefinePSInstance(iNodeInstance, iOutputInstance);

							if (isShiftTranslationVectorApplied)
							{
								ModifyInstanceTransform(iNodeInstance, shiftTranslationVector, iOutputInstance);
								iRefIDToShiftTranslationMap[refID].first = isShiftTranslationVectorApplied;
								std::copy(std::begin(shiftTranslationVector), std::end(shiftTranslationVector), std::begin(iRefIDToShiftTranslationMap[refID].second));
							}
							else
							{
								//Case where reference is already defined but have shift translation vector
								const auto unorderedMapIterator = iRefIDToShiftTranslationMap.find(refID);
								if (unorderedMapIterator != iRefIDToShiftTranslationMap.end())
								{
									isShiftTranslationVectorApplied = unorderedMapIterator->second.first;
									if (isShiftTranslationVectorApplied)
									{
										shiftTranslationVector[0] = unorderedMapIterator->second.second[0];
										shiftTranslationVector[1] = unorderedMapIterator->second.second[1];
										shiftTranslationVector[2] = unorderedMapIterator->second.second[2];
										ModifyInstanceTransform(iNodeInstance, shiftTranslationVector, iOutputInstance);
									}
								}
							}

							oOutputReference.AddInstance(iOutputInstance);
							numDefined += entities;
						}
					}
				}
			}
			if( _g_transferMiscData)
			{
				SPAIopPSReferenceMiscData asmReferenceMiscData = iInputReference.GetMiscData();
				SPAIopMiscOutputAsmData refOutputMiscData;
				TransferAssemblyReferenceMiscData( asmReferenceMiscData, refOutputMiscData);
				oOutputReference.SetOutputSubAsmLevelMiscData( refOutputMiscData);
			}
		}

        // In some formats supporting delayed part loading, we do not get reference name without loading (importing) part first. So set it after part is imported.
        SPAIopWString referenceName = iInputReference.GetReferenceName();
        oOutputReference.SetName( referenceName );

		// mark this reference as fully defined so we don't go through this code again if it is used by another instance
		if (numDefined > 0) 
		{
			oOutputReference.SetDefined(true);
		}
	} 
	else 
	{
		// Don't do anything for previously defined refs, but pretend they were defined this time
		numDefined = 1;
	}

	opNumDefined = numDefined;
	return result;
}

//-----------------------------------------------------------------------------------------

bool ImportProductStructure(const wchar_t*& ipSourceFilePath, const wchar_t*& ipSourceDocType, const SPAIopPolicy& iPolicy, SPAIopInputProductStructure& oInputPS, SPAIopPSImportResult &oInputPSResult)
{
	bool returnValue = true;
	// Import the Assembly tree as a Product Structure.  Note that 
	// importing a Product Structure does not import any brep data associated
	// with the model, which is done separately from part-associated reference nodes
	// in the Product Structure.  Use try/catch exception handling blocks to 
	// manage fatal errors.

	// Create Source Document from input file
	std::wstring sourceFilePath(ipSourceFilePath);
	SPAIopWString wSourceFilePath( sourceFilePath.c_str());
	SPAIopWString wSourceDocType(ipSourceDocType);
	SPAIopDocument sourceDoc(wSourceFilePath, wSourceDocType);

	// Create a Product Strcuture Importer
	SPAIopProductStructureImporter psImporter;

	try
	{
		UserProgressCallbackClass localCallackClass;

		if(_g_hasUserData)
			psImporter.SetProgressCallBackWithUserData((SPAIopProgressCallbackWithUserData)MyProgressCallBackWithUserData, &localCallackClass);
		else
			psImporter.SetProgressCallBack((SPAIopProgressCallback)MyProgressCallBack);

		oInputPSResult = psImporter.ImportWithPolicy(iPolicy, sourceDoc, oInputPS);
		SPAIopLengthUnitType inputPSUnit	= oInputPS.GetUnits();
		if( SPAIopLengthUnit_Unknown != inputPSUnit )
		{
			SPAIopWString unitStr = SPAIopLengthUnitUtils::LengthUnitToString( inputPSUnit );
			const wchar_t * pUnitArray = NULL;
			unitStr.ToWChar(pUnitArray);
			std::wcout << "Product Structure Units: "<< pUnitArray <<std::endl;
		}

		DumpMissingFilesInProductStructure( oInputPSResult);

		if( _g_pProgressReporter != NULL )
		{
			_g_pProgressReporter->SetNumParts(oInputPS.GetUniqueReferencesCount());
			size_t numNonMissingParts = GetNonMissingParts(oInputPS,oInputPSResult);
		    _g_pProgressReporter->setNonMissingParts( numNonMissingParts );
			_g_pProgressReporter->SetPsDone(true);
		}

		if (_g_hasUserData)
		{
			size_t count = localCallackClass.GetCount();
			PrintMessage(L"\n\nUser data count, after ImportWithPolicy, is :%zd\n", count);
		}

		if (_g_hasUserData)
			psImporter.SetProgressCallBackWithUserData((SPAIopProgressCallbackWithUserData)MyProgressCallBackWithUserData, static_cast<void*>(NULL));
	}
	catch ( SPAIopAcisError& error )
	{
		DisplayError( error );
		returnValue = false;
	}
	catch ( ... )
	{
		std::wcout << "FAILURE: Unknown exception caught! " << std::endl;
		returnValue = false;
	}

	return returnValue;
}
//-----------------------------------------------------------------------------------------

size_t GetNonMissingParts(SPAIopInputProductStructure& iInputPSStructure, SPAIopPSImportResult &iInputPSResult)
{
	size_t missingParts = 0;
	size_t totalParts = iInputPSStructure.GetUniqueReferencesCount();
	SPAIopInputPSMissingFileIter missingFiles = iInputPSResult.GetMissingFileIterator();   
	int nInstances=iInputPSStructure.GetUniqueReferencesCount();
	if (nInstances > 0)
	{
		int i=0;
		for( i = 0; i < nInstances; i++ )
		{
			
			SPAIopInputPSReference uniqueReference = iInputPSStructure.GetUniqueReference(i);
			SPAIopWString referenceStorageName = uniqueReference.GetStorageName();
			bool isMissingReference = missingFiles.ContainsPath( referenceStorageName );
			if ( true == isMissingReference )
			{
				if(uniqueReference.IsPart())
				{
					missingParts++;
				}

			}
		}
				
	}
	return ( totalParts - missingParts );

}

//-----------------------------------------------------------------------------------------

int ImportPart(SPAIopInputPSReference& iInputReference, const SPAIopPolicy& iPolicy, ENTITY_LIST& oAcisEntities, SPAIopPartImportResult& oPartImportResult)
{
	// Import the brep data of a part into the given AcisEntities using the Import 
	// method of a SPAIopPartDocument object.  Use try/catch exception handling blocks to 
	// manage fatal errors.
	int result = SPAIopSampleResult_Import_Ok;
	SPAIopAcisPartImporter partImporter;
	SPAIopPartImportResult partImportResult;

	unsigned int uniqueID = iInputReference.GetUniqueID();
	SPAIopWString storageNamePath = iInputReference.GetStorageName();
	
	SPAIopWString storageName;
	GetNameWithExtension(storageNamePath, storageName);

	const wchar_t* pStorageName = NULL;
	storageName.ToWChar(pStorageName);

	try
	{
		PrintMessage(L"\n Processing reference part. UniqueID = %d, Storage Name = %ls \n", uniqueID, pStorageName);

		SPAIopPartDocument iopPartDocument = iInputReference.GetDocument();
		partImportResult = partImporter.ImportWithPolicy(iPolicy, iopPartDocument, oAcisEntities);	

		// NOTE: The reference name is only valid after the part's import operation is successful.
		SPAIopWString referenceName = iInputReference.GetReferenceName();
		const wchar_t* pReferenceName = NULL;
		referenceName.ToWChar(pReferenceName);

		PrintMessage(L"\n Processed reference part. Reference Name = %ls \n", pReferenceName);
	}
	catch ( SPAIopAcisError& error )
	{
		DisplayError( error );
		PrintMessage(L"\n FAILURE : Part failed to import. UniqueID = %d, Storage Name = %ls \n", uniqueID, pStorageName);
		result = SPAIopSampleResult_Import_Part_Fail;
		oAcisEntities.clear();
	}
	catch ( ... )
	{
		PrintMessage(L"\n FAILURE : Part failed to import. UniqueID = %d, Storage Name = %ls \n", uniqueID, pStorageName);
		result = SPAIopSampleResult_Import_Part_Fail;
	}
	
	oPartImportResult = partImportResult;
	return result;
}

//-----------------------------------------------------------------------------------------

void ImportVisu( SPAIopPartDocument& iAcisInteropPartDocument, const SPAIopPolicy& iPolicy, SPAIopVisuData& oVisuData, bool& oIsShiftTranslationVectorApplied, double(&oShiftTranslation)[3])
{
	// Import the visualization data of a part into the given SPAIopVisuData using the Import 
	// method of a SPAIopVisualizationImporter object.  Use try/catch exception handling blocks to 
	// manage fatal errors.

	SPAIopVisualizationImporter visuImporter;
	try
	{
		SPAIopVisuImportResult importRes = visuImporter.ImportWithPolicy(iPolicy, iAcisInteropPartDocument);

		oVisuData = importRes.GetVisualizationData();

		const SPAIopLengthUnitType visualizationUnit = oVisuData.GetUnits();
		if (visualizationUnit != SPAIopLengthUnit_Unknown)
		{
			SPAIopWString unitStr = SPAIopLengthUnitUtils::LengthUnitToString(visualizationUnit);
			const wchar_t* pUnitArray = NULL;
			unitStr.ToWChar(pUnitArray);
			PrintMessage(L"\nUnit of Visualization Data:- %ls\n", pUnitArray);
		}

		if (!oIsShiftTranslationVectorApplied)
		{
			oIsShiftTranslationVectorApplied = importRes.GetShiftTranslation(oShiftTranslation[0], oShiftTranslation[1], oShiftTranslation[2]);
			if (oIsShiftTranslationVectorApplied)
			{
				PrintMessage(L"\nGeometry is shifted near the origin to fit it inside the current modeler range, Following Shift Translation Vector should be applied on the part to get the original input coordinates of geometry:- X: %lf\tY: %lf\tZ: %lf\n", oShiftTranslation[0], oShiftTranslation[1], oShiftTranslation[2]);

				//Scaling Shift translation vector to modeler unit as we will accomodate it as instance transform, instance transform is given in modeler unit
				double unitScale = 1.0;
				SPAIopLengthUnitType modelerUnit = SPAIopLengthUnit_Unknown;
				SPAIopModelerOptionsAcis acisModelerOptions;
				const bool res = iPolicy.GetCurrentModelerOptions(acisModelerOptions);
				if (res)
				{
					const bool foundOption = acisModelerOptions.GetCurrentModelerUnits(modelerUnit);
					if (!foundOption)
						modelerUnit = SPAIopLengthUnit_Millimeter;
				}
				else
					modelerUnit = SPAIopLengthUnit_Millimeter;

				unitScale = SPAIopLengthUnitUtils::GetScalingFactor(visualizationUnit, modelerUnit);
				oShiftTranslation[0] = oShiftTranslation[0] * unitScale;
				oShiftTranslation[1] = oShiftTranslation[1] * unitScale;
				oShiftTranslation[2] = oShiftTranslation[2] * unitScale;
			}
		}
	}
	catch ( SPAIopAcisError& error )
	{
		DisplayError( error );
	}
	catch ( ... )
	{
		std::wcout << "FAILURE: Unknown exception caught! " << std::endl;
	}
}

//-----------------------------------------------------------------------------------------

int test_IopAcisImportExport(int argc, char* argv[])
{
	int result(1);
	SPAIopPerformanceStatistics printPerfoStatsObj;

	// Work-flow similar to Acis based app where Acis will be generally initialized before IOP is called.
	api_start_modeller(0);

	try
	{
		char* argFields[] = { "i", "o", "p", "t", "v", "h", "misc", "mode", "itype", "otype", "log", "progressCallbackMode"};
		int size = sizeof(argFields) / sizeof(*argFields);
		SPAIopArgParser parser(size, argFields, argc, argv, &PrintHelp);
		// IOP is called in its own scope.
		// This sample uses a guard class that initializes InterOp upon 
		// construction and terminates InterOp upon destruction.  All 
		// translations are done within the boundaries of a single 
		// initialization and shutdown.  InterOp may only be initialized 
		// and shutdown once per process.  Please do not use the guard 
		// classes multiple times per process.
		SPAIopAcisSystemInitGuard initGuard(SPATIAL_LICENSE);

		result = initGuard.GetInitStatus();

		if (SPA_UNLOCK_NO_MSG == result)
		{
			// Spatial products have been unlocked
			std::wcout << L"SUCCESS: SPAIopAcisSystem::Initialize. " << std::endl;
		}
		else if (SPA_UNLOCK_KEY_WARN_EXPIRE == result)
			std::wcout << L"SPAIopAcisSystem::Initialize returned SPA_UNLOCK_KEY_WARN_EXPIRE; your license key will expire soon!\n";
		else if (SPA_UNLOCK_REGISTRATION_WARN_EXPIRE == result)
			std::wcout << L"SPAIopAcisSystem::Initialize returned SPA_UNLOCK_REGISTRATION_WARN_EXPIRE; your registration will expire soon.\n";
		else
		{
			std::wcout << L"SPAIopAcisSystem::Initialize returned an unexpected value; Contact Spatial Customer Support or your Account Manager.\n";
			return 1;
		}
		result = 1;


		

		if (true == parser.IsSuccess())
		{
			const wchar_t* pSourceFilePath = NULL;
			bool hasInputFileName = parser.GetWStringValueFor("i", pSourceFilePath);

			const wchar_t* pTargetFilePath = NULL;
			bool hasOutputFileName = parser.GetWStringValueFor("o", pTargetFilePath);

			const wchar_t* pPolicyFilePath = NULL;
			bool hasPolicyFileName = parser.GetWStringValueFor("p", pPolicyFilePath);

			const wchar_t* pLogFilePath = NULL;
			bool hasLogFileName = parser.GetWStringValueFor("log", pLogFilePath);

			const wchar_t* pSourceDocType = NULL;
			bool hasInputDocType = parser.GetWStringValueFor("itype", pSourceDocType); // GetStringValueFor

			const wchar_t* pTargetDocType = NULL;
			bool hasOutputDocType = parser.GetWStringValueFor("otype", pTargetDocType); // GetStringValueFor

			int timeFlag = 0;
			bool hasTimeFlag = parser.GetIntegerValueFor("t", timeFlag);

			int visuFlag = 0;
			bool hasVisuFlag = parser.GetIntegerValueFor("v", visuFlag);

			int miscFlag = 0;
			bool miscDataFlag = parser.GetIntegerValueFor("misc", miscFlag);

			const wchar_t* pInputMode = L"";
			bool modeFlag = parser.GetWStringValueFor("mode", pInputMode);

			int userDataFlag = 0; 
			bool hasUserDataFlag = parser.GetIntegerValueFor("progressCallbackMode", userDataFlag);

			if (wcsncmp(pInputMode, L"MP", 2) == 0)
				_g_runInMP = true;

			time_t timer0;
			time(&timer0);

			if (hasVisuFlag && (visuFlag != 0))
			{
				_g_doingVisualization = true;
			}

			if (miscDataFlag && (miscFlag != 0))
			{
				_g_transferMiscData = true;
			}

			if (hasUserDataFlag && (userDataFlag != 0))
			{
				_g_hasUserData = true;
			}

			if (hasInputFileName && hasOutputFileName && pSourceFilePath && pTargetFilePath)
			{
				SPAIopPolicy importPolicy;
				SPAIopPolicy exportPolicy;
				if (0 != pPolicyFilePath)
				{
					// SRE27: Internal Testing : Pass two policy files to -p by using | as delimiter 
					// First policy to use for importation and second policy to use for exportation
					// Collect the policy files separated by | from pPolicyFilePath in policyArray
					std::vector<wchar_t*> policyArray;
					wchar_t* pPolicyTemp = (wchar_t*)pPolicyFilePath;
					wchar_t delim[] = L"|";
					wchar_t* pPointer = NULL;
					wchar_t* pToken = wcstok(pPolicyTemp, delim, &pPointer);
					while (pToken)
					{
						policyArray.push_back(pToken);
						pToken = wcstok(NULL, delim, &pPointer);
					}

					// If policyArray is not empty then pass 1st policy as import policy
					if (!policyArray.empty())
					{
						SPAIopWString policyFilePath(policyArray[0]);
						SPAIopPolicyFileStreamXML xmlPolicyStream;
						xmlPolicyStream.SetUnstreamFilePath(policyFilePath);
						importPolicy.Deserialize(xmlPolicyStream);
					}

					//If Policy array size is 1 then pass that single policy for import as well as export
					if (policyArray.size() == 1)
						exportPolicy = importPolicy;
					else if (policyArray.size() == 2)
					{
						//If Policy array size is 2 then pass second policy file as export policy
						SPAIopWString policyFilePathExport(policyArray[1]);
						SPAIopPolicyFileStreamXML xmlPolicyStreamExport;
						xmlPolicyStreamExport.SetUnstreamFilePath(policyFilePathExport);
						exportPolicy.Deserialize(xmlPolicyStreamExport);						
					}
				}

				SetOrUpdateLogFileInPolicy(pLogFilePath, importPolicy);
				SetOrUpdateLogFileInPolicy(pLogFilePath, exportPolicy);

				result = ImportAndExportModel(pSourceFilePath, pTargetFilePath, pSourceDocType, pTargetDocType, importPolicy, exportPolicy);
				// When **completely** done with these entities, release them.
				// This is especially useful when sequentially importing and processing many parts in one session.
				//for (auto&& elist : toBeReleased) // nice, if your compiler supports it
				for (std::vector<ENTITY_LIST*>::iterator iter = toBeReleased.begin(); iter != toBeReleased.end(); ++iter)
				{
					SPAIopAcisSystem::ReleasePartEntities(**iter);
					ACIS_DELETE *iter;
				}
				toBeReleased.clear();
			}
			if (hasTimeFlag && (timeFlag != 0))
			{
				time_t timer1;
				time(&timer1);
				timer1 -= timer0;
				std::wcout << L"Time in seconds: " << timer1 << std::endl;
			}
		}

	}
	catch (SPAIopAcisError& fatalErr)
	{
		DisplayError(fatalErr);
		result = 1;
	}
	catch (...)
	{
		PrintMessage(L"FAILURE: Unknown exception caught! ");
		result = 1;
	}

	api_stop_modeller();

    return result;
} 

//-----------------------------------------------------------------------------------------

